/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using System.Qizx.Xml;
using DataModelException = System.Qizx.Api.DataModelException;
using Node = System.Qizx.Api.Node;
using FileUtil = System.Qizx.Util.Basic.FileUtil;
namespace System.Qizx.Xdm {

    /// <summary> Provides access to the data-model of XML documents.
    /// <para>
    /// Expects resolved document URI/path.</para>
    /// <para>
    /// Parsed documents are managed in a cache with configurable size (see
    /// {@link #setCacheSize}).</para>
    /// </summary>
    public class DocumentPool : DocumentParser {
        private const int MIN_CACHE_SIZE = 128 * 1024;
        private const string CACHE_SIZE_PROP = "com.qizx.docpool.maxsize";
        // default size in Mb, configurable by system prop:
        private int cacheSize = 12 * 1048576;
        protected internal System.Collections.ArrayList cache = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
        protected internal System.Collections.ArrayList timeCache = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10)); // parse time for files

        /// <summary> Creates an empty document pool.</summary>
        public DocumentPool() {
            initSize();
        }

        public virtual int getCacheSize() {
            return cacheSize;
        }

        /// <summary> Defines the maximal memory size of the document cache. This size is
        /// otherwise defined by the system property
        /// "com.qizx.docman.cachesize".
        /// 
        /// </summary>
        /// <param name="size">in bytes (hint).
        /// </param>
        public virtual void setCacheSize(int size) {
            lock (this) {
                cacheSize = System.Math.Max(size, MIN_CACHE_SIZE);
            }
        }

        private void initSize() {
            string sysp = System.Environment.GetEnvironmentVariable(CACHE_SIZE_PROP);
            if (sysp != null) {
                try {
                    setCacheSize(System.Int32.Parse(sysp));
                } catch (System.Exception ignored) {
                    // ignored
                }
            }
        }

        public virtual void clearCache() {
            lock (this) {
                cache = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
                timeCache = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
            }
        }

        /// <summary> 
        /// Cached access by URI.
        /// </summary>
        /// <param name="uri">an absolute uri (no more resolving) WITH PROTOCOL
        /// </param>
        /// <returns> the root node (or document node) of the document.
        /// </returns>
        public virtual Node findDocumentNode(string uri) {
            FONIDataModel doc = findDocument(uri);
            return doc.DocumentNode;
        }

        /// <summary> Cached access by URI.</summary>
        /// <param name="uri">now a resolved uri with protocol, correctly escaped
        /// </param>
        /// <throws>  DataModelException </throws>
        public virtual FONIDataModel findDocument(string uri) {
            try {
                FONIDataModel dm = getCachedDocument(uri);
                if (dm == null) {
                    dm = new FONIDataModel(parseDocument(new XmlSource(uri)));
                }
                System.Int64 tempAux = getFileDate(uri);
                cacheDocument(dm, ref tempAux);
                return dm;
            } catch (System.Xml.XmlException sax) {
                throw new DataModelException("XML parsing error in " + uri + ": " + sax.Message, sax.InnerException);
            } catch (System.Exception e) {
                throw new DataModelException("Document access error in " + uri + ": " + e.Message, e);
            }

            // 	long T0 = System.currentTimeMillis();
            // 	System.err.println("load, start encoding");
            // 	CoreByteOutput out = new CoreByteOutput();
            // 	new TreeEncoder().encode(new FONIDataModel(dm).documentNode(), out);
            // 	out.close();
            // 	long T1 = System.currentTimeMillis();
            // 	System.err.println("ENCODED in "+(T1-T0)+" ms");
            // 	XMLSerializer handler = new XMLSerializer();
            // 	new TreeDecoder(new CoreByteInput(out)).decode(handler);
            // 	long T2 = System.currentTimeMillis();
            // 	System.err.println("DECODED in "+(T2-T1)+" ms");
        }

        // returns null if not found OR stale
        protected internal virtual FONIDataModel getCachedDocument(string uri) {
            lock (this) {
                // linear search: never mind!
                for (int d = 0, D = cache.Count; d < D; d++) {
                    FONIDataModel doc = (FONIDataModel)cache[d];

                    try {
                        string baseURI = doc.Dom.BaseURI;
                        if (uri.Equals(baseURI)) {
                            cache.RemoveAt(d);
                            System.Object tempObject;
                            tempObject = timeCache[d];
                            timeCache.RemoveAt(d);
                            System.Int64 readTime = (System.Int64)tempObject;
                            System.Int64 fileDate = getFileDate(baseURI);
                            if (fileDate != null && readTime != null && (long)fileDate > (long)readTime) {
                                return null; // already removed
                            }
                            // put at head:
                            cache.Insert(0, doc);
                            timeCache.Insert(0, readTime);
                            return doc;
                        }
                    } catch (DataModelException shouldNotHappen) {
                        SupportClass.WriteStackTrace(shouldNotHappen, Console.Error);
                    }
                }
                return null;
            }
        }

        private System.Int64 getFileDate(string baseURI) {
            // if file, check the date:
            if (!baseURI.StartsWith("file:")) {
                return 0;
            }
            System.IO.FileInfo file = FileUtil.urlToFile(baseURI);
            if (file == null)
            // malformed?
			{
                return 0;
            }
            return (long)((file.LastWriteTime.Ticks - 621355968000000000) / 10000);
        }

        protected internal virtual void cacheDocument(FONIDataModel doc, ref System.Int64 fileDate) {
            lock (this) {
                int pos = cache.IndexOf(doc);
                if (pos > 0) {
                    cache.RemoveAt(pos);
                    timeCache.RemoveAt(pos);
                }
                cache.Insert(0, doc);
                timeCache.Insert(0, fileDate);
                int cumulatedSize = 0;
                for (int d = 1, D = cache.Count; d < D; d++) {
                    FONIDataModel doc2 = (FONIDataModel)cache[d];

                    try {
                        int size = doc2.Dom.estimateMemorySize();
                        if (cumulatedSize + size > cacheSize) {
                            SupportClass.SetCapacity(cache, d);
                            break;
                        }
                        cumulatedSize += size;
                    } catch (DataModelException shouldNotHappen) {
                        SupportClass.WriteStackTrace(shouldNotHappen, Console.Error);
                    }
                }
            }
        }
    }
}